/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands.call.animation;

import android.opengl.Matrix;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.ApplicationData;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.commands.XREObjectCommandHandler;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.BackInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.BounceInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.CircularInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.CubicInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.ElasticInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.ExponentialInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.QuadraticInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.QuarticInterpolator;
import com.hercules.android.receiver.handlers.commands.call.animation.interpolators.SineInterpolator;
import com.hercules.android.receiver.handlers.events.EventRaiser;
import com.hercules.android.receiver.handlers.events.EventRaisingException;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.events.XREAnimationEvent;
import com.hercules.android.receiver.model.objects.XREAbsoluteScaleAnimation;
import com.hercules.android.receiver.model.objects.XREAbsoluteTranslationAnimation;
import com.hercules.android.receiver.model.objects.XREAlphaAnimation;
import com.hercules.android.receiver.model.objects.XREAnimationEasingFunction;
import com.hercules.android.receiver.model.objects.XREAnimationResource;
import com.hercules.android.receiver.model.objects.XREDimensionsAnimation;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XRETransformAnimation;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.views.AndroidXREView;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
 * This class handles animation call command. It will perform animation for given command, and when the animation ends,
 * it will use XREViewFinderUtility to find all affected views, and use XREObjectPainter to paint each affected view. It
 * extends XREObjectCommandHandler.
 * <p>
 * <b>Sample Usage:</b>
 *
 * <pre>
 * // create instance
 * XREAnimationCallCommandHandler handler = new XREAnimationCallCommandHandler();
 *
 * // inject required field
 * handler.setLogger(Logger.getLogger(&quot;logger&quot;));
 * handler.setAffectedViewFinder(new XREAnimationViewFinderUtility());
 * handler.setObjectPainters(new HashMap&lt;String, XREObjectPainter&gt;());
 * handler.getObjectPainters().put(&quot;XREView&quot;, new XREAnimationCallCommandHandlerUnitTests.MockPainter());
 *
 * // optionally you can inject easingFunctionToInterpolator map
 *
 * // set command and receiver to correct values
 * XRECallCommand command = COMMAND;
 * AndroidReceiver receiver = RECEIVER;
 *
 * try {
 *     // process command
 *     handler.processCommandMessage(command, receiver);
 *
 *     // handler will process animation resource, that is present at
 *     // receiver.applicationData.elementsXREIdToObject with ID, passed as first parameter in command.params
 *     // it will create corresponding animation and update XREView if needed
 * } catch (XREAnimationCallCommandHandlerException e) {
 *     // process exception
 *     e.printStackTrace();
 * }
 * </pre>
 * <p>
 * <b>Thread Safety:</b> This class is mutable because both its parent and itself contain mutable fields, it's not
 * technically thread-safe. It would be thread-safe if all the configurable fields do not change after initialization.
 *
 * @author semi_sleep, 7realm
 * @version 1.0
 */
public class XREAnimationCallCommandHandler extends XREObjectCommandHandler {
    /**
     * Represents the name of current class.
     * <p>
     * Initialized to simple name of the class at construction.
     */
    private final String className = getClass().getName();

    /**
     * The {@link EventRaiser} used to raise XRE events.
     * <p>
     * Has getter and setter.
     * <p>
     * Usage: It is used in handleAnimationEnd.
     * <p>
     * AndroidAssembly2 new field
     */
    private EventRaiser eventRaiser;

    /**
     * The represents the mapping from easing function to interpolator.
     * <p>
     * Cannot be null / empty, cannot contain null / empty key or value, should contain interpolator for every easing
     * function defined in XREAnimationEasingFunction enum. It's initialized in the constructor with default values, can
     * be changed by its public setter. It's used in the processCommandMessage() method, and is also exposed to external
     * codes by its public getter.
     * <p>
     * AndroidAssembly2 renamed to shorter name
     */
    private Map<XREAnimationEasingFunction, Interpolator> easingInterpolator =
        new HashMap<XREAnimationEasingFunction, Interpolator>();

    /**
     * Creates an instance of XREAnimationCallCommandHandler.
     */
    public XREAnimationCallCommandHandler() {
        // create the easing function to interpolator mapping
        easingInterpolator.put(XREAnimationEasingFunction.LINEAR_IN, new LinearInterpolator());
        easingInterpolator.put(XREAnimationEasingFunction.LINEAR_OUT, new LinearInterpolator());
        easingInterpolator.put(XREAnimationEasingFunction.LINEAR_IN_OUT, new LinearInterpolator());
        easingInterpolator.put(XREAnimationEasingFunction.QUAD_IN, new QuadraticInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.QUAD_OUT, new QuadraticInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.QUAD_IN_OUT, new QuadraticInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.CUBIC_IN, new CubicInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.CUBIC_OUT, new CubicInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.CUBIC_IN_OUT, new CubicInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.QUART_IN, new QuarticInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.QUART_OUT, new QuarticInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.QUART_IN_OUT, new QuarticInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.EXPO_IN, new ExponentialInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.EXPO_OUT, new ExponentialInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.EXPO_IN_OUT, new ExponentialInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.SINE_IN, new SineInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.SINE_OUT, new SineInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.SINE_IN_OUT, new SineInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.ELASTIC_IN, new ElasticInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.ELASTIC_OUT, new ElasticInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.ELASTIC_IN_OUT, new ElasticInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.CIRC_IN, new CircularInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.CIRC_OUT, new CircularInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.CIRC_IN_OUT, new CircularInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.BOUNCE_IN, new BounceInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.BOUNCE_OUT, new BounceInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.BOUNCE_IN_OUT, new BounceInterpolator(true, true));
        easingInterpolator.put(XREAnimationEasingFunction.BACK_IN, new BackInterpolator(true, false));
        easingInterpolator.put(XREAnimationEasingFunction.BACK_OUT, new BackInterpolator(false, true));
        easingInterpolator.put(XREAnimationEasingFunction.BACK_IN_OUT, new BackInterpolator(true, true));
    }

    /**
     * Gets the mapping from easing function to interpolator.
     *
     * @return the copy of required mapping
     */
    public Map<XREAnimationEasingFunction, Interpolator> getEasingFunctionToInterpolator() {
        return new HashMap<XREAnimationEasingFunction, Interpolator>(easingInterpolator);
    }

    /**
     * Sets the mapping from easing function to interpolator.
     *
     * @param easingFunctionToInterpolator the mapping
     * @throws IllegalArgumentException if easingFunctionToInterpolator is null, or if easingFunctionToInterpolator
     * contains null key or value, or if easingFunctionToInterpolator do not contain interpolator for every easing
     * function defined in XREAnimationEasingFunction enum
     */
    public void setEasingFunctionToInterpolator(
        Map<XREAnimationEasingFunction, Interpolator> easingFunctionToInterpolator) {
        // check argument
        Helper.checkNotNull(easingFunctionToInterpolator, "easingFunctionToInterpolator");

        // create set that will contain all values of XREAnimationEasingFunction enum
        Set<XREAnimationEasingFunction> allEasyFunctions =
            new HashSet<XREAnimationEasingFunction>(Arrays.asList(XREAnimationEasingFunction.values()));

        // check keys and value
        for (Entry<XREAnimationEasingFunction, Interpolator> entry : easingFunctionToInterpolator.entrySet()) {
            if (entry.getKey() == null) {
                throw new IllegalArgumentException("Argument 'easingFunctionToInterpolator' contains null key.");
            }
            if (entry.getValue() == null) {
                throw new IllegalArgumentException("Argument 'easingFunctionToInterpolator' contains null value.");
            }

            // remove enum value
            allEasyFunctions.remove(entry.getKey());
        }

        // check if some values of XREAnimationEasingFunction left
        if (!allEasyFunctions.isEmpty()) {
            throw new IllegalArgumentException("Argument 'easingFunctionToInterpolator' does not contain"
                + " interpolator(s) for " + allEasyFunctions + ".");
        }

        // assign shallow copy to field
        this.easingInterpolator = new HashMap<XREAnimationEasingFunction, Interpolator>(easingFunctionToInterpolator);
    }

    /**
     * Gets the {@link EventRaiser} used to raise XRE events.
     *
     * @return the {@link EventRaiser} used to raise XRE events
     */
    public EventRaiser getEventRaiser() {
        return eventRaiser;
    }

    /**
     * Sets the {@link EventRaiser} used to raise XRE events.
     *
     * @param eventRaiser the {@link EventRaiser} used to raise XRE events
     * @throws IllegalArgumentException if parameter is null
     */
    public void setEventRaiser(EventRaiser eventRaiser) {
        // check argument
        Helper.checkNotNull(eventRaiser, "eventRaiser");

        this.eventRaiser = eventRaiser;
    }

    /**
     * Process the given command message. The given command is expected to be a XRECallCommand and its method field
     * should be "animate". This method will perform animation for corresponding view, it will also create a listener
     * for the animation, so that after the animation ends, properties of XREView can be updated and all the affected
     * views can be repainted.
     *
     * @param command the XRECommand object which should be a call command to call the "animate" method on XREView
     * @param receiver the AndroidReceiver object containing reference to application data to be used while processing
     * command message
     * @throws IllegalArgumentException if command or receiver is null, or if command is not instance of XRECallCommand,
     * or if command properties are invalid
     * @throws IllegalStateException if any property defined in base classes (e.g. objectPainters, logger) is invalid
     * and prevents current method from executing
     * @throws XREAnimationCallCommandHandlerException if any error occurs
     */
    @Override
    public void processCommandMessage(XRECommand command, final AndroidReceiver receiver)
        throws XREAnimationCallCommandHandlerException {

        final String methodName = "processCommandMessage";
        try {
            // check arguments
            Helper.checkNotNull(receiver, "receiver");
            final XRECallCommand callCommand = checkCommand(command);
            final int targetId = callCommand.getTargetId();

            // retrieve data from receiver
            ApplicationData appData = getAppData(receiver);
            final AndroidXREView view = getView(targetId, appData);
            final XREView xreView = getXREView(targetId, appData);
            final XREAnimationResource animationResource = getAnimationResource(callCommand, appData);
            
            // perform animation
            view.clearAnimation();
/*
            xreView.getMatrix()[0][3] += xreView.getInternalTranslationX();
            xreView.getMatrix()[1][3] += xreView.getInternalTranslationY();
            xreView.setInternalTranslationX(0);
            xreView.setInternalTranslationY(0);
  */          
            // initialize animation
            Animation animation = null;
            if (animationResource instanceof XREAlphaAnimation) {
                animation = initAlphaAnimation(xreView, animationResource);
            } else if (animationResource instanceof XREDimensionsAnimation) {
                animation = initDimAnimation(view, xreView, animationResource);
            } else if (animationResource instanceof XREAbsoluteScaleAnimation) {
                animation = initAbsoluteScaleAnimation(xreView, animationResource);
            } else if (animationResource instanceof XRETransformAnimation) {
                animation = initTransformAnimation(xreView, animationResource, receiver);
            } else if (animationResource instanceof XREAbsoluteTranslationAnimation) {
                animation = initAbsoluteTranslationAnimation(xreView, animationResource, receiver);
            } else {
            	LogUtil.error(getLogger(), XREAnimationCallCommandHandler.this, methodName,
            			new XREAnimationCallCommandHandlerException("Type of given animation resource '"
                		+ animationResource.getClass() + "' is not supported."));
            }
            // set the duration
            animation.setDuration(animationResource.getDuration());

            // retrieve interpolator and set it to animation
            if (animationResource.getEasing() != null) {
                Interpolator interpolator = easingInterpolator.get(animationResource.getEasing());
                animation.setInterpolator(interpolator);
            }

            xreView.setPlayingAnimation(true);
            xreView.getAndroidXREView().updateLayer();
                    
            // create a animation listener and set it to animation
            AnimationListener listener =
                new XREAnimationListener(animationResource.getId(), targetId, receiver);
            animation.setAnimationListener(listener);
            // schedule animation
            view.startAnimation(animation);
            LogUtil.info(getLogger(), XREAnimationCallCommandHandler.this, methodName,
                "Start of animation[{0}], XREView {1}.", animationResource.getId(), targetId);

        } catch (IllegalArgumentException e) {
            throw Helper.logError(getLogger(), className, methodName, e);
        } catch (XREAnimationCallCommandHandlerException e) {
            throw Helper.logError(getLogger(), className, methodName, e);
        }
    }

    /**
     * This method will be called after animation ends, it will update XREView's properties, and then use
     * XREViewFinderUtility to find all the affected view, and finally use corresponding XRE painters to repaint each
     * affected view.
     * <p>
     * AndroidAssembly3 refactoring and fixed issue when animation resource is deleted before animation has ended
     *
     * @param animationId the id of target animation
     * @param xreViewId the id of target XREView
     * @param receiver the AndroidReceiver object containing reference to application data to be used
     * @throws IllegalArgumentException if command or receiver is null, or if command properties are invalid
     * @throws IllegalStateException if any property defined in base classes (e.g. objectPainters, logger) is invalid
     * and prevents current method from executing
     * @throws XREAnimationCallCommandHandlerException if any error occurs
     */
    @SuppressWarnings("boxing")
    void handleAnimationEnd(int animationId, int xreViewId, AndroidReceiver receiver, Animation animation)
        throws XREAnimationCallCommandHandlerException {
        String methodName = "handleAnimationEnd";
        try {
            // check state
            checkState(eventRaiser, "eventRaiser");

            // check arguments
            Helper.checkNotNull(receiver, "receiver");

            // check if animation resource is present
            ApplicationData appData = getAppData(receiver);
            XREObject animationResource = appData.getElementsXREIdToObject().get(animationId);

            LogUtil.info(getLogger(), this, methodName, "End of animation[{0}], XREView {1}.", animationId, xreViewId);

            if (animationResource == null) {
                LogUtil.warn(getLogger(), this, methodName, "Animation {0} was deleted before it's end was handled.",
                    animationId);
            } else if (!appData.getElementsXREIdToObject().containsKey(xreViewId)) {
                LogUtil.warn(getLogger(), this, methodName, "The view {0} for animation {1} was deleted "
                    + "before it's end was handled.", xreViewId, animationId);
            } else {
                // get views
                final XREView xreView = getXREView(xreViewId, appData);
                final AndroidXREView view = getView(xreViewId, appData);

                // AndroidAssembly3 removed animation resources that do not need modifications
                if (animationResource instanceof XREDimensionsAnimation) {
                    // update dimensions
                    XREDimensionsAnimation a = (XREDimensionsAnimation) animationResource;
                    xreView.setDimensions(new int[]{a.getWidth(), a.getHeight()});

                    // update layout
                    view.updateLayout();
                } else if (animationResource instanceof XREAlphaAnimation) {
                    // update alpha
                    XREAlphaAnimation a = (XREAlphaAnimation) animationResource;
                    xreView.setAlpha(a.getAlpha());
                } else if (animationResource instanceof XRETransformAnimation) {
                    XRETransformAnimation transformAnimation = (XRETransformAnimation) animationResource;
                    // get the matrix
                    float[] matrix = AnimationUtil.toOneDimensionalArray(xreView.getMatrix());

                    matrix[12] += xreView.getInternalTranslationX();
                    matrix[13] += xreView.getInternalTranslationY();
                    
                    // translate to action point
                    float dx = transformAnimation.getActionPointX();
                    float dy = transformAnimation.getActionPointY();
                    Matrix.translateM(matrix, 0, dx, dy, 0);

                    // scale matrix
                    Matrix.scaleM(matrix, 0, (float) transformAnimation.getScaleX(), (float) transformAnimation.getScaleY(), 1);

                    // rotate matrix
                    Matrix.rotateM(matrix, 0, (float) Math.toDegrees(transformAnimation.getRotation()), 0, 0, 1);

                    // translate to origin and to (x, y) immediately
                    Matrix.translateM(matrix, 0, -dx, -dy, 0);

                    Matrix.translateM(matrix, 0, transformAnimation.getX(), transformAnimation.getY(), 0);
                    
                    // update translation
                    xreView.setInternalTranslationX(matrix[12] - (float) xreView.getMatrix()[0][3]);
                    xreView.setInternalTranslationY(matrix[13] - (float) xreView.getMatrix()[1][3]);

                    matrix[12] = (float) xreView.getMatrix()[0][3];
                    matrix[13] = (float) xreView.getMatrix()[1][3];
                    
                    // set the updated matrix to XRE view
                    xreView.setMatrix(AnimationUtil.toTwoDimensionalArray(matrix));
                }
/*
                xreView.getMatrix()[0][3] += xreView.getInternalTranslationX();
                xreView.getMatrix()[1][3] += xreView.getInternalTranslationY();
                xreView.setInternalTranslationX(0);
                xreView.setInternalTranslationY(0);
*/                
                xreView.setPlayingAnimation(false);
                xreView.getAndroidXREView().updateActualMatrix();
                //xreView.getAndroidXREView().updateLayer();
                
                // create event
                XREAnimationEvent event = new XREAnimationEvent();
                event.setName(XREAnimationEvent.ON_COMPLETE_EVENT_NAME);
                event.setSource(animationId);
                event.setView(xreViewId);

                // raise event
                eventRaiser.raiseEvent(event, receiver, true);
            }

        } catch (IllegalStateException e) {
            throw Helper.logError(getLogger(), className, methodName, e);
        } catch (IllegalArgumentException e) {
            throw Helper.logError(getLogger(), className, methodName, e);
        } catch (XREAnimationCallCommandHandlerException e) {
            throw Helper.logError(getLogger(), className, methodName, e);
        } catch (EventRaisingException e) {
            throw Helper.logError(getLogger(), className, methodName, new XREAnimationCallCommandHandlerException(
                "Error occurred while raising '" + XREAnimationEvent.ON_COMPLETE_EVENT_NAME + "' animation event.", e));
        }
    }

    /**
     * Initializes alpha animation.
     *
     * @param xreView the current XRE view
     * @param animationResource the alpha animation resource
     * @return initialized animation
     */
    private static Animation initAlphaAnimation(final XREView xreView, XREAnimationResource animationResource) {
        XREAlphaAnimation alphaAnimation = (XREAlphaAnimation) animationResource;

        // create alpha animation
        Animation animation = new AlphaAnimation((float) xreView.getAlpha(), (float) alphaAnimation.getAlpha()) {
            /**
             * Changes the alpha property of the supplied Transformation.
             *
             * @param interpolatedTime the value of the normalized time (0.0 to 1.0) after it has been run through the
             * interpolation function
             * @param t the Transformation object to fill in with the current transforms
             */
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                super.applyTransformation(interpolatedTime, t);

                // apply alpha at every step
                xreView.setAlpha(t.getAlpha());
            }
            
            /**
             * Indicates if animation will change it bounds. In our case it changes them.
             *
             * @return always true
             */
            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };

        // do not fill animation values because we change alpha at every step
        animation.setFillEnabled(false);
        return animation;
    }

    /**
     * Initializes dimensions animation.
     * <p>
     * AndroidAssembly2 this method was changed, because scale animation is not dimension animation
     *
     * @param view the corresponding android view
     * @param xreView the XREView
     * @param animationResource the dimensions animation resource
     * @return initialized animation
     */
    private static Animation initDimAnimation(final AndroidXREView view, final XREView xreView,
        XREAnimationResource animationResource) {
        final XREDimensionsAnimation dimensionsAnimation = (XREDimensionsAnimation) animationResource;

        // ensure layout
        view.updateLayout(Math.max(xreView.getWidth(), dimensionsAnimation.getWidth()), Math.max(xreView
            .getHeight(), dimensionsAnimation.getHeight()));

        // create animation
        Animation animation =
            new DimensionsAnimation(xreView, dimensionsAnimation.getWidth(), dimensionsAnimation.getHeight());

        // do not set filling, because we will change the dimensions at every animation step
        animation.setFillEnabled(false);
        return animation;
    }

    /**
     * Initializes absolute scale animation.
     *
     * @param xreView the current XRE view
     * @param animationResource the absolute scale animation resource
     * @return initialized animation
     */
    private static Animation initAbsoluteScaleAnimation(final XREView xreView, XREAnimationResource animationResource) {
        final XREAbsoluteScaleAnimation absoluteScaleAnimation = (XREAbsoluteScaleAnimation) animationResource;
        // get current scale
        Vector3d scale = AnimationUtil.getCurrentScale(xreView);

        // old absolute scale values
        final float oldScaleX = xreView.getAbsoluteScaleX();
        final float oldScaleY = xreView.getAbsoluteScaleY();

        // desired absolute scale values
        final float destScaleX = absoluteScaleAnimation.getSx();
        final float destScaleY = absoluteScaleAnimation.getSy();

        // create scale animation from old values to new
        Animation animation =
            new ScaleAnimation(scale.getX(), destScaleX / oldScaleX * scale.getX(), scale.getY(), destScaleY
                / oldScaleY * scale.getY()) {
                /**
                 * Changes the scale transformation of supplied Transformation.
                 *
                 * @param interpolatedTime the value of the normalized time (0.0 to 1.0) after it has been run through
                 * the interpolation function
                 * @param t the Transformation object to fill in with the current transforms
                 */
                @Override
                protected void applyTransformation(float interpolatedTime, Transformation t) {
                    super.applyTransformation(interpolatedTime, t);

                    // update XREView
                    float newScaleX = oldScaleX + (destScaleX - oldScaleX) * interpolatedTime;
                    float newScaleY = oldScaleY + (destScaleY - oldScaleY) * interpolatedTime;
                    xreView.setAbsoluteScaleX(newScaleX);
                    xreView.setAbsoluteScaleY(newScaleY);

                    // get transformed values
                    float[] values = new float[3 * 3];
                    t.getMatrix().getValues(values);

                    // update XREView matrix
                    int index = 0;
                    for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 3; j++) {
                            xreView.getMatrix()[i][j] = values[index++];
                        }
                    }
                }
            };

        // do not fill animation values because we change matrix at every step
        animation.setFillEnabled(false);
        return animation;
    }

    /**
     * Initializes transformation animation.
     * <p>
     * AndroidAssembly2 method is changed to support current method of drawing
     *
     * @param xreView the current XRE view
     * @param animationResource the transformation animation resource
     * @return initialized animation
     */
    private static Animation initTransformAnimation(final XREView xreView, final XREAnimationResource animationResource, 
    		final AndroidReceiver receiver) {
        XRETransformAnimation transformAnimation = (XRETransformAnimation) animationResource;

        // create an animation set with share interpolator flag set to true
        AnimationSet animation = new AnimationSet(true);

        // calculate current scale
        Vector3d scale = AnimationUtil.getScale(AnimationUtil.toOneDimensionalArray(xreView.getMatrix()));

        // create scale animation
        Animation scaleAnimation =
            new ScaleAnimation(scale.getX(), (float) transformAnimation.getScaleX(), scale.getY(),
                (float) transformAnimation.getScaleY(), transformAnimation.getActionPointX(), transformAnimation
                    .getActionPointY());

        // calculate current rotation
        Vector3d rotation = AnimationUtil.getRotation(AnimationUtil.toOneDimensionalArray(xreView.getMatrix()));

        // create rotate animation
        Animation rotateAnimation =
            new RotateAnimation((float) Math.toDegrees(rotation.getZ()), (float) Math.toDegrees(rotation.getZ()
                + transformAnimation.getRotation()), transformAnimation.getActionPointX(), transformAnimation
                .getActionPointY());

        // create translate animation
        Animation translateAnimation = new TranslateAnimation(xreView.getInternalTranslationX(), 
        	xreView.getInternalTranslationX() + transformAnimation.getX(), xreView.getInternalTranslationY(), 
        	xreView.getInternalTranslationY() + transformAnimation.getY());

        // add to animation set
        animation.addAnimation(scaleAnimation);
        animation.addAnimation(rotateAnimation);
        animation.addAnimation(translateAnimation);

        // do not fill animation because we change XREView matrix at every animation step
        animation.setFillEnabled(false);
        return animation;
    }

    /**
     * Initializes absolute translation animation.
     * <p>
     * AndroidAssembly3 new method to support {@link XREAbsoluteTranslationAnimation}
     *
     * @param xreView the current XRE view
     * @param animationResource the absolute translation animation resource
     * @return initialized animation
     */
    private static Animation initAbsoluteTranslationAnimation(final XREView xreView,
        XREAnimationResource animationResource, final AndroidReceiver receiver) {
        XREAbsoluteTranslationAnimation absTranslationAnimation = (XREAbsoluteTranslationAnimation) animationResource;

        // create translate animation
        Animation animation = new TranslateAnimation(xreView.getInternalTranslationX(), absTranslationAnimation.getX() - xreView.getViewX(), 
            xreView.getInternalTranslationY(), absTranslationAnimation.getY() - xreView.getViewY()) {
                /**
                 * The transformation of an animation set is the concatenation of all of its component animations.
                 *
                 * @param interpolatedTime the interpolated time
                 * @param t a transformation object that is provided by the caller and will be filled in by the
                 * animation
                 */
                @Override
                protected void applyTransformation(float interpolatedTime, Transformation t) {
                    super.applyTransformation(interpolatedTime, t);

                    // get transformed values
                    float[] values = new float[3 * 3];
                    t.getMatrix().getValues(values);

                    // set translation
                    xreView.setInternalTranslationX(values[2]);
                    xreView.setInternalTranslationY(values[5]);
                }
            };

        // do not fill animation because we change XREView matrix at every animation step
        animation.setFillEnabled(false);
        return animation;
    }

    /**
     * Checks state of given property.
     *
     * @param obj the property value to check
     * @param name the name of the property
     * @throws IllegalStateException if property value is null
     */
    private static void checkState(Object obj, String name) {
        if (obj == null) {
            throw new IllegalStateException("Value of property '" + name + "' is null.");
        }
    }

    /**
     * Checks if given object is not null.
     *
     * @param object the object to check
     * @param name the name of checked object
     * @throws XREAnimationCallCommandHandlerException if object is null
     */
    private static void checkNotNull(Object object, String name) throws XREAnimationCallCommandHandlerException {
        if (object == null) {
            throw new XREAnimationCallCommandHandlerException("Value of '" + name + "' is null.");
        }
    }

    /**
     * Checks XRECallCommand.
     *
     * @param command the command to check
     * @return command, casted to {@link XRECallCommand}
     * @throws IllegalArgumentException if command is invalid
     */
    private static XRECallCommand checkCommand(XRECommand command) {
        XRECallCommand result = Helper.checkCommand(command);
        Helper.checkNotNull(result.getParams(), "command.params");
        if (result.getParams().isEmpty()) {
            throw new IllegalArgumentException("Argument 'command.params' must not be empty.");
        }

        // check first parameter
        Object firstParam = result.getParams().get(0);
        Helper.checkNotNull(firstParam, "command.params[0]");
        if (firstParam instanceof Integer) {
            return result;
        }

        throw new IllegalArgumentException("First parameter of call command must be integer XRE object ID.");
    }

    /**
     * Checks given XRE object, if is not null and has correct type.
     *
     * @param <T> the required type of XRE object
     * @param xreObj the object to check
     * @param name the name of the object to check
     * @param targetClass required class of given object
     * @return passed in object, casted to target type
     * @throws XREAnimationCallCommandHandlerException if object is null or has wrong type
     */
    private static <T> T checkAndCast(XREObject xreObj, String name, Class<T> targetClass)
        throws XREAnimationCallCommandHandlerException {
        checkNotNull(xreObj, name);
        if (!targetClass.isInstance(xreObj)) {
            throw new XREAnimationCallCommandHandlerException("Object '" + name + "' must be instance of "
                + targetClass.getSimpleName() + " class.");
        }
        return targetClass.cast(xreObj);
    }

    /**
     * Gets application data from android receiver.
     *
     * @param receiver android receiver
     * @return application data, stored in receiver
     * @throws XREAnimationCallCommandHandlerException if application data is incorrect or not present
     */
    private static ApplicationData getAppData(AndroidReceiver receiver) throws XREAnimationCallCommandHandlerException {
        // get application data
        ApplicationData appData = receiver.getApplicationData();
        checkNotNull(appData, "receiver.applicationData");
        checkNotNull(appData.getElementsXREIdToObject(), "receiver.applicationData.elementsXREIdToObject");
        checkNotNull(appData.getViewsXREIdToView(), "receiver.applicationData.viewsXREIdToView");
        return appData;
    }

    /**
     * Gets view from application data.
     *
     * @param targetId the target of call command
     * @param appData application data
     * @return required view
     * @throws XREAnimationCallCommandHandlerException if view is not present
     */
    @SuppressWarnings("boxing")
    private static AndroidXREView getView(int targetId, ApplicationData appData)
        throws XREAnimationCallCommandHandlerException {
        // get view
        AndroidXREView view = appData.getViewsXREIdToView().get(targetId);
        checkNotNull(view, "receiver.applicationData.viewsXREIdToView[" + targetId + "]");
        return view;
    }

    /**
     * Gets XRE view from application data.
     *
     * @param targetId the target of call command
     * @param appData application data
     * @return required XRE view
     * @throws XREAnimationCallCommandHandlerException if XRE view is not present or has incorrect type or has incorrect
     * matrix
     */
    @SuppressWarnings("boxing")
    private static XREView getXREView(int targetId, ApplicationData appData)
        throws XREAnimationCallCommandHandlerException {
        // get XRE view
        XREObject xreViewObj = appData.getElementsXREIdToObject().get(targetId);
        XREView xreView =
            checkAndCast(xreViewObj, "receiver.applicationData.elementsXREIdToObject[" + targetId + "]", XREView.class);

        // AndroidAssembly2 the check of matrix size is removed,
        // because it is impossible for matrix to be incorrect size at this point
        checkNotNull(xreView.getMatrix(), "XREView.matrix");

        return xreView;
    }

    /**
     * Gets animation resource from application data.
     *
     * @param callCommand the used call command
     * @param appData application data
     * @return required animation resource
     * @throws XREAnimationCallCommandHandlerException if animation resource is not present or has incorrect type
     */
    private static XREAnimationResource getAnimationResource(XRECallCommand callCommand, ApplicationData appData)
        throws XREAnimationCallCommandHandlerException {
        // get XRE animation resource
        Object firstParam = callCommand.getParams().get(0);
        XREObject xreAnimResObj = appData.getElementsXREIdToObject().get(firstParam);
        XREAnimationResource animationResource =
            checkAndCast(xreAnimResObj, "receiver.applicationData.elementsXREIdToObject[" + firstParam + "]",
                XREAnimationResource.class);
        return animationResource;
    }

    /**
     * This class implements AnimationListener to listen for the animation end event, when animation ends, it will call
     * handleAnimationEnd() method.
     * <p>
     * AndroidAssembly2 moved to {@link XREAnimationCallCommandHandler}
     * <p>
     * <b>Thread Safety:</b> This class is immutable and thread safe.
     *
     * @author semi_sleep, 7realm
     * @version 1.0
     */
    private class XREAnimationListener implements AnimationListener {
        /**
         * The represents the animation id.
         * <p>
         * Can not be null. It's set in constructor and cannot be changed after that. It's used in the onAnimationEnd()
         * method.
         */
        private final int animationId;

        /**
         * The represents the target xre view id.
         * <p>
         * Can not be null. It's set in constructor and cannot be changed after that. It's used in the onAnimationEnd()
         * method.
         */
        private final int xreViewId;

        /**
         * The represents the android receiver.
         * <p>
         * Can not be null. It's set in constructor and cannot be changed after that. It's used in the onAnimationEnd()
         * method.
         */
        private final AndroidReceiver receiver;
        
        /**
         * Creates an instance of XREAnimationListener using given parameters.
         *
         * @param animationId the id of target animation
         * @param xreViewId the id of target XREView
         * @param receiver the android receiver
         * @throws IllegalArgumentException if commandHandler, command or receiver is null
         */
        public XREAnimationListener(int animationId, int xreViewId, AndroidReceiver receiver) {
            // check arguments
            Helper.checkNotNull(receiver, "receiver");

            // assign data to fields
            this.receiver = receiver;
            this.animationId = animationId;
            this.xreViewId = xreViewId;
        }

        /**
         * Notifies the start of the animation.
         *
         * @param animation the started animation
         */
        public void onAnimationStart(Animation animation) {
            // does nothing
        }

        /**
         * Notifies the repetition of the animation.
         *
         * @param animation the animation which was repeated
         * @throws IllegalArgumentException if animation is null
         */
        public void onAnimationRepeat(Animation animation) {
            // does nothing
        }

        /**
         * Notifies the end of the animation.
         *
         * @param animation the animation which reached its end
         * @throws IllegalArgumentException if animation is null
         * @throws XREAnimationListenerException if any error occurs on animation end
         */
        public void onAnimationEnd(Animation animation) {
            // check argument
            Helper.checkNotNull(animation, "animation");

            try {
	            // notify about animation end
	            handleAnimationEnd(animationId, xreViewId, receiver, animation);
            } catch (XREAnimationCallCommandHandlerException e) {
                throw new XREAnimationListenerException("Error occurred while handling animation end.", e);
            } catch (IllegalStateException e) {
                throw new XREAnimationListenerException("Animation command handler is not in correct state.", e);
            }
        }
    }

    /**
     * This exception is thrown by XREAnimationListener if any error occurs while handling animation event. It extends
     * RuntimeException.
     * <p>
     * AndroidAssembly2 moved to {@link XREAnimationCallCommandHandler} class
     * <p>
     * <b>Thread safety:</b> This class is not thread safe because its base class is not thread safe.
     *
     * @author semi_sleep, 7realm
     * @version 1.0
     */
    private class XREAnimationListenerException extends RuntimeException {
        /** Serial version UID. */
        private static final long serialVersionUID = 7648622983145206263L;

        /**
         * Creates a new instance of this exception with error message and inner cause.
         *
         * @param message the detailed error message of this exception
         * @param cause the inner cause of this exception
         */
        public XREAnimationListenerException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}